﻿using System;
using WikiDoc.Formatter;
using WikiDoc.Parser;
using WikiDoc.Reader;

namespace WikiDoc
{
    /// <summary>
    /// 
    /// </summary>
    public class WikiTranslater
    {
        /// <summary>
        /// 初始化 WikiTranslater 类的新实例。
        /// </summary>
        /// <param name="defaultParser"></param>
        public WikiTranslater(IParser defaultParser)
        {
            if (defaultParser == null) throw new ArgumentNullException("defaultParser");

            _DefaultParser = defaultParser;
        }

        /// <summary>
        /// 初始化 WikiTranslater 类的新实例。
        /// </summary>
        public WikiTranslater():this(new CodeParser())
        {
        }

        private readonly ParserCollection _Parsers = new ParserCollection();
        /// <summary>
        /// Gets the parsers.
        /// </summary>
        /// <value>The parsers.</value>
        public ParserCollection Parsers
        {
            get
            {
                return _Parsers;
            }
        }

        private IParser _DefaultParser;
        /// <summary>
        /// Gets or sets the default parser.
        /// </summary>
        /// <value>The default parser.</value>
        public IParser DefaultParser
        {
            get
            {
                return _DefaultParser;
            }
            set
            {
                _DefaultParser = value;
            }
        }

        /// <summary>
        /// Parses the specified STR.
        /// </summary>
        /// <param name="str">The STR.</param>
        /// <returns></returns>
        public string Parse(string str)
        {
            System.IO.TextWriter writer = new System.IO.StringWriter();
            IFormatter formatter = new XmlDocFormatter(writer);
            System.IO.TextReader reader = new System.IO.StringReader(str);

            Parse(reader, formatter);

            writer.Flush();
            string ret = writer.ToString();
            reader.Close();
            writer.Close();

            return ret;
        }

        /// <summary>
        /// Parses the specified stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="formatter">The formatter.</param>
        public void Parse(System.IO.TextReader stream, IFormatter formatter)
        {
            if (stream == null) throw new ArgumentNullException("stream");
            if (formatter == null) throw new ArgumentNullException("formatter");

            WikiReader reader = new WikiReader(stream);

            reader.ReadLine();

            while (reader.CurrentLine.Type != LineType.Eof)
            {
                WikiLine info = reader.CurrentLine;

                switch (info.Type)
                {
                    case LineType.Normal:
                        ParseParagraph(reader, formatter);
                        break;
                    case LineType.Empty:
                        reader.ReadLine();
                        break;
                    case LineType.BulletList:
                        ParseList(LineType.BulletList, reader, formatter);
                        break;
                    case LineType.NumberList:
                        ParseList(LineType.NumberList, reader, formatter);
                        break;
                    case LineType.AlphaList:
                        ParseList(LineType.AlphaList, reader, formatter);
                        break;
                    case LineType.Indent:
                        ParseList(LineType.Indent, reader, formatter);
                        break;
                    case LineType.Header:
                        HeaderLine iheader = info as HeaderLine;
                        formatter.BeginTag ("h" + iheader.Level);
                        formatter.AppendContent(iheader.Content);
                        formatter.EndTag("h" + iheader.Level);
                        reader.ReadLine();
                        break;
                    case LineType.HorizonLine:
                        formatter.BeginTag("hr");
                        formatter.EndTag("hr");
                        reader.ReadLine();
                        break;
                    case LineType.CodeStart:
                        ParseCode(reader, formatter);
                        break;
                    case LineType.CodeEnd:
                        ParseParagraph(reader, formatter);
                        break;
                    default:
                        throw new InvalidOperationException("不可能到达这里！");
                }
            }
        }


        /// <summary>
        /// 读取一个完整的代码段，退出时当前行为代码段后的第一行
        /// </summary>
        private void ParseCode(WikiReader reader, IFormatter formatter)
        {
            if (reader.CurrentLine.Type != LineType.CodeStart)
            {
                return;
            }

            CodePicker code = new CodePicker(reader.BaseReader);

            CodeStartLine iline = reader.CurrentLine as CodeStartLine;

            string parserName = iline.ParserName.ToLower();
            string argv = iline.Argv;

            IParser parser;

            if (_Parsers.Contains(parserName))
            {
                parser = _Parsers[parserName];
            }
            else
            {
                parser = _DefaultParser;
            }

            parser.Process(argv, code, formatter);
            reader.ReadLine();
        }


        /// <summary>
        /// 读取一个完整的列表，直到列表结束，退出时当前行是下一个片段起始行
        /// </summary>
        /// <param name="listType">Type of the list.</param>
        /// <param name="reader">The reader.</param>
        /// <param name="formatter">The formatter.</param>
        private static void ParseList(LineType listType, WikiReader reader, IFormatter formatter)
        {
            if (reader.CurrentLine.Type != listType)
            {
                return;
            }

            bool itemStarted = false;
            int level;
            if (listType == LineType.Indent)
            {
                IndentLine info = reader.CurrentLine as IndentLine;
                level = info.Level;
            }
            else
            {
                ListLine info = reader.CurrentLine as ListLine;
                level = info.Level;
            }

            BeginList(listType, formatter);

            while (reader.CurrentLine.Type != LineType.Eof)
            {
                switch (reader.CurrentLine.Type)
                {
                        // 一个空行表示一个列表项的结束
                    case LineType.Empty:
                        if (itemStarted)
                        {
                            EndListItem(ref itemStarted, formatter);
                        }

                        // 跳过后续的所有空行
                        do
                        {
                            formatter.BeginTag("br");
                            formatter.EndTag("br");
                            reader.ReadLine();
                        } while (reader.CurrentLine.Type == LineType.Empty);

                        continue;

                        // 列表中遇到列表定义行
                        // 如果缩进级别相同，则说明是本列表的一个新列表项
                        // 如果缩进级别更高，则说明要开始一个新的下一级列表
                        // 如果缩进级别更低，则说明本列表已经结束
                    case LineType.BulletList:
                    case LineType.NumberList:
                    case LineType.AlphaList:
                        {
                            ListLine info = reader.CurrentLine as ListLine;

                            if (info.Level > level)
                            {
                                ParseList(reader.CurrentLine.Type, reader, formatter);
                                EndListItem(ref itemStarted, formatter);
                                continue;
                            }
                            else if (info.Level < level)
                            {
                                goto ListEnd;
                            }

                            if (itemStarted)
                            {
                                EndListItem(ref itemStarted, formatter);
                            }

                            BeginListItem(ref itemStarted, formatter);
                            formatter.AppendContent(info.Content);
                        }
                        break;

                        // 列表中遇到缩进行
                        // 如果缩进级别更高，则看作是本列表的列表项或续行
                        // 如果缩进级别更低，则说明本列表结束
                    case LineType.Indent:
                        {
                            IndentLine info = reader.CurrentLine as IndentLine;

                            if (info.Level >= level)
                            {
                                if (!itemStarted)
                                {
                                    BeginListItem(ref itemStarted, formatter);
                                }
                                formatter.AppendContent(info.Content);
                            }
                            else
                            {
                                goto ListEnd;
                            }
                        }
                        break;

                        // 其他情况，视为列表结束
                    case LineType.Normal:
                    case LineType.Header:
                    case LineType.HorizonLine:
                    case LineType.CodeStart:
                    case LineType.CodeEnd:
                        goto ListEnd;
                    default:
                        break;
                }

                reader.ReadLine();
            }

        ListEnd:
            if (itemStarted)
            {
                EndListItem(ref itemStarted, formatter);
            }
            EndList(listType, formatter);
        }

        private static void BeginList(LineType listType, IFormatter formatter)
        {
            switch (listType)
            {
                case LineType.BulletList:
                    formatter.BeginTag("ul");
                    break;
                case LineType.NumberList:
                    formatter.BeginTag("ol");
                    break;
                case LineType.AlphaList:
                    formatter.BeginTag("ol");
                    break;
                case LineType.Indent:
                    formatter.BeginTag("ul");
                    break;
                default:
                    throw new ArgumentOutOfRangeException("listType", listType, "不是有效的列表类型");
            }
        }

        private static void EndList(LineType listType, IFormatter formatter)
        {
            switch (listType)
            {
                case LineType.BulletList:
                    formatter.EndTag("ul");
                    break;
                case LineType.NumberList:
                    formatter.EndTag("ol");
                    break;
                case LineType.AlphaList:
                    formatter.EndTag("ol");
                    break;
                case LineType.Indent:
                    formatter.EndTag("ul");
                    break;
                default:
                    throw new ArgumentOutOfRangeException("listType", listType, "不是有效的列表类型");
            }
        }

        private static void BeginListItem(ref bool started, IFormatter formatter)
        {
            System.Diagnostics.Debug.Assert(!started);

            formatter.BeginTag("li");
            started = true;
        }

        private static void EndListItem(ref bool started, IFormatter formatter)
        {
            System.Diagnostics.Debug.Assert(started);

            formatter.EndTag("li");
            started = false;
        }


        /// <summary>
        /// 获取一个完整的段落，直到段落结束，函数退出后当前行是段落结束后的第一行。
        /// 如果进入函数时当前行不是一个普通段落行，则当前行不变。
        /// </summary>
        private static void ParseParagraph(WikiReader reader, IFormatter formatter)
        {
            bool started = false;

            while (reader.CurrentLine.Type != LineType.Eof)
            {

                switch (reader.CurrentLine.Type)
                {
                    case LineType.Normal:
                    case LineType.CodeEnd:
                        WikiLine info = reader.CurrentLine;
                        string line = info.Line;

                        if (!started)
                        {
                            started = true;
                            formatter.BeginTag("para");
                        }
                        formatter.AppendContent(line);
                        reader.ReadLine();
                        break;
                    default:
                        goto ParagraphEnd;
                }
            }

        ParagraphEnd:
            if (started)
            {
                formatter.EndTag("para");
            }
            return;
        }
    }
}
